#include <asm-generic/errno.h>
#include <complex.h>
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include "common.h"
#include "display.h"
extern "C" {
#include "yy.h"
}
#include "nlist.h"
#include "item.h"
#include "pnode.h"
#include "dlink.h"
#include "flink.h"
#include "sizer.h"
#include "pcoder.h"

Dlink *dataLink = NULL;	// data link
Flink *funcLink = NULL;	// func link;

#define SET_COMMENT(p)	if(((node*)(p))->id.src) src = ((node*)(p))->id.src
#define CLR_COMMENT(p)	if(((node*)(p))->id.src == src) src = NULL



Pcoder :: Pcoder()
{
	memset(this, 0, sizeof(Pcoder));
	funcLink = new Flink();
	// load sysy lib funcs
	loadLibFuncs();
	dimOffsetArray[0] = 4; dimOffsetArray[1] = 4; dimOffsetArray[2] = 4;
	dimOffsetArray[3] = 4; dimOffsetArray[4] = 4;
	 dataLink = new Dlink(VAR_DLINK);	// global variable list
	curDlink = dataLink;				// current variable list
	curFnode = NULL;
	constGroup = new Const();
}

Pcoder :: ~Pcoder() {
	CLEAR();
	delete constGroup;
	delPnodes(&mainPcode);
	delPnodes(&initPcode);
	delPnodes(&constPcode);
}

void Pcoder :: buildArrayOffset(Item* ip, listNode_t* np, int* dimVect){
	if (!dimVect) return;
	baseOffset = 0, lastDepth = 0;
	// is full init
	for (int i = 0; i < dimVect[0]; i ++) dimOffsetArray[i + 1] = dimVect[dimVect[0] - i] * dimOffsetArray[i];
	foreachNodeArray(ip,np, 0);
}

void Pcoder :: foreachNodeArray(Item* ip ,listNode_t* np, int depth){
	if (depth == 2){ errPrint("array init dim most is two,most dim is two!!!, not support three dim"); }
	if (np->nops > 0){
		for (int i = 0; i < np->nops; i++){
			node* n1 = np->ptr[i];
			if (n1 == NULL) continue;
			if (n1->type == NODE_LIST){
				foreachNodeArray(ip,(listNode_t*)n1, depth + 1);
			} else {
				run(n1);	
				Item* it1 = POP();
				int dimOffset = dimOffsetArray[depth];
				int r = baseOffset % dimOffset;
				if (depth == 1){
					if(!r || lastDepth != depth){
						baseOffset = (baseOffset / dimOffset + (r > 0)) * dimOffset + 4;
					} else baseOffset += 4;
					it1->attr->offset = baseOffset - 4;
				}
				r = baseOffset % dimOffset;
				if (i == np->nops - 1 && depth == 1){
					if (r) baseOffset = (baseOffset / dimOffset + (r > 0)) * dimOffset;
					else baseOffset += 4;
				} 
				if (depth == 0) {baseOffset += 4; it1->attr->offset = baseOffset - 4;}
				lastDepth = depth;
				Pnode* pnp = new Pnode('=');
				pnp->items[0] = cloneIdItem(ip);
				pnp->items[0]->attr->offset = it1->attr->offset;
				pnp->items[1] = it1;
				addPnode(&mainPcode, pnp);
			}
		}
	}
				
}

void Pcoder :: loadLibFuncs(){
	Fnode* f1 = new Fnode((char*)"getint",0);
	f1->attr = newAttr(INT);
	funcLink->add(f1);


	Fnode* f2 = new Fnode((char*)"getch",0);
	f2->attr = newAttr(INT);
	funcLink->add(f2);
	
	
	Fnode* f3 = new Fnode((char*)"getfloat",0);
	f3->attr = newAttr(FLOAT);
	funcLink->add(f3);

	Fnode* f4 = new Fnode((char*)"getarray",0);
	f4->attr = newAttr(INT);
	node* f4_parp = idNode((char*)"a1");
	f4_parp->id.attr = newAttr(INT);
	Dnode *dp = f4->dlink->add(f4_parp, f4_parp->id.attr, 0);
	dp->func = f4->name;
	funcLink->add(f4);

	Fnode* f5 = new Fnode((char*)"putint",0);
	f5->attr = newAttr(VOID);
	node* f5_parp = idNode((char*)"a1");
	f5_parp->id.attr = newAttr(INT);
	Dnode* f5_dp = f5->dlink->add(f5_parp, f5_parp->id.attr, 0);
	dp->func = f5->name;
	funcLink->add(f5);

	Fnode* f6 = new Fnode((char*)"putch",0);
	f6->attr = newAttr(VOID);
	funcLink->add(f6);

	Fnode* f7 = new Fnode((char*)"putfloat",0);
	f7->attr = newAttr(INT);
	funcLink->add(f7);

	Fnode* f8 = new Fnode((char*)"putarray",0);
	f8->attr = newAttr(INT);
	funcLink->add(f8);

	Fnode* f9 = new Fnode((char*)"putfloat",0);
	f9->attr = newAttr(INT);
	funcLink->add(f9);
	
	Fnode* f10 = new Fnode((char*)"putarray",0);
	f10->attr = newAttr(VOID);
	funcLink->add(f10);

	Fnode* f11 = new Fnode((char*)"putarray",0);
	f11->attr = newAttr(VOID);
	funcLink->add(f11);

	Fnode* f12 = new Fnode((char*)"putf",0);
	f12->attr = newAttr(VOID);
	funcLink->add(f12);
}



void Pcoder :: errPrint (const char *msg, char *opt_msg)
{
	printf ("[ERROR] ");
	if ( src ) printf ("%s, #%d ... ", src->fileName, src->lineNum );
	printf ("%s", msg);	if ( opt_msg ) printf (" - %s", opt_msg);
	printf ("\n");
	errorCount++;
}

void Pcoder :: warningPrint (const char *msg, char *opt_msg)
{
	printf ("[WARNING] ");
	if ( src ) printf ("%s, #%d ... ", src->fileName, src->lineNum );
	printf ("%s", msg);	if ( opt_msg ) printf (" - %s", opt_msg);
	printf ("\n");
	warningCount++;
}

void Pcoder :: takeSrc(node *np)
{
	SET_COMMENT(np);
	if ( np->id.src )
	{
		char buf[256];
		sprintf(buf, " #%d: ", src->lineNum);
		std::string str = src->fileName;
		str += buf; str += src->srcCode;
		Pnode *pnp = new Pnode(P_SRC_CODE);
		pnp->items[0] = strItem((char*)str.c_str());
		addPnode(&mainPcode, pnp);
	}
}

void Pcoder :: run(node *np)
{
	if ( np == NULL ) return;
	Dnode *dp;
	Fnode *fp;
	Item  *iptr;

	takeSrc(np);
	switch ( np->type )
	{
		case NODE_CON:	// const Node
			PUSH(intItem(np->con.value, cloneAttr(np->con.attr)));
			break;

		case NODE_STR:	// string Node
			iptr = idItem(constGroup->add(np->str.str));
			iptr->attr = newAttr(CHAR);
			iptr->type = IMMD_ITEM;
			iptr->attr->dataBank = CONST;
			PUSH(iptr);
			break;

		case NODE_ID:	// ID node
			dp = curDlink->search(np->id.name, WHOLE_SEARCH);
			fp = funcLink->search(np->id.name);
			if ( dp )		// found in data link
			{
				Item *ip = (dp->attr->dimVect)? immdItem(dp->nameStr(), dp->attr):
											    idItem  (dp->nameStr(), dp->attr);
				ip->fname = dp->func;
				ip->vname = dp->name;
				ip->home = (void*)dp;
				PUSH(ip);
			}
			else if ( fp )	// found in function link
			{
				Item *ip = lblItem(np->id.name);
				ip->home = (void*)fp;
				PUSH(ip);
			}
			else
				errPrint("name not found!", np->id.name);
			break;

		case NODE_LIST:	// list Node
			for (int i = 0; i < np->list.nops; i++)
				run(np->list.ptr[i]);
			break;

		case NODE_OPR:	// operator Node
			run1((oprNode_t *)np);
			break;
	}
}

void Pcoder :: run1(oprNode_t *op)
{
	attrib *ap = op->attr;
	char *fname;
	node *np;

	switch ( op->oper )
	{
		case ';':
			addPnode(&mainPcode, new Pnode(';'));
			CLEAR();
			// clear Temp Index mask
			memset(&tempIndexMask, 0, sizeof(tempIndexMask));
			break;

		case '{':
			{
				Dlink *lk = new Dlink(VAR_DLINK);

				curDlink->addChild(lk);
				curDlink = lk;
				run(op->op[0]) ;
				curDlink = lk->parent;
			}
			break;

		case DATA_DECL:
			if ( op->nops > 0 && op->op[0] )
			{
				node *np = op->op[0];	// variables id node
				attrib *ap = op->attr;
				int  mem_addr = -1;

				char *dname = np->id.name;	// data name
				Dnode *dp = curDlink->search(dname, LOCAL_SEARCH);

				if ( dp ) // variable check
				{
					if ( cmpAttr(dp->attr, op->attr) )
					{
						errPrint("data type conflict!", dname);
						break;
					}

					if ( !dp->dimCheck(np->id.dim) )
					{
						errPrint("data dimension error!", dname);
						break;
					}

				}
				else if ( funcLink && funcLink->search(dname) ) // func variable check
				{
					errPrint("name redefined!", dname);
					break;
				}

				if ( dp == NULL )
				{
					if ( curFnode )
						dp = curDlink->add(np, ap, ++curFnode->dIndex);
					else 
					{ 
						dp = curDlink->add(np, ap, 0);
						dp->attr->isStatic = -1;
					}


				}
				
				dp->dimUpdate((node*) np->id.dim);

				dp->func = curFnode? curFnode->name: NULL;

				if ( np->id.init )	// data initialization ...
				{
					if ( dp->func && !ap->isStatic && ap->dataBank != CONST )
					{
						int depth = DEPTH();
						// handle array
						if (np->id.init->type == NODE_LIST){
							Item *ip = idItem(dp->nameStr(), dp->attr);
							ip->fname = dp->func;
							ip->vname = dp->name;
							attrib* att1 = newAttr(INT);
							passParameter(att1, OPR_NODE(op, 0), 0, 2);
							node* par2 = conNode(0, INT);
							attrib* att2 = newAttr(INT);
							passParameter(att2, par2, 1, 2);
							node* par3 = conNode(sizer(ap, TOTAL_SIZE), INT);
							attrib* att3 = newAttr(INT);
							passParameter(att3, par3, 2, 2);
							Item *ip2 = strItem((char*)"memset");
							Pnode *pnp = new Pnode(CALL, ip2);
							addPnode (&mainPcode, pnp);

							buildArrayOffset(ip,(listNode_t*)np->id.init, dp->attr->dimVect);
						}else {
							run(np->id.init);
							if ( DEPTH() == (depth+1) )
							{
								Item *ip = idItem(dp->nameStr(), dp->attr);
								ip->home = dp;
								ip->fname = dp->func;
								ip->vname = dp->name;
								Pnode *pnp = new Pnode('=', ip);
								pnp->items[1] = POP();
								addPnode(&mainPcode, pnp);
							}
						}
					}						
					
					//else if ( ap->dataBank == CONST )
							//romDataInit(dname, dp->attr, np->id.init, dp, mem_addr);
					else
						ramDataInit(dname, dp->attr, np->id.init, dp, mem_addr);
				}
				else if(dp->attr->isStatic){
					run(np); Item* ip0 = POP();
					Pnode* pnp = new Pnode(G_P_DEF, ip0);	
					addPnode(&initPcode, pnp);
				}
				else if ( ap->dataBank == CONST )
				{
					errPrint("uninitialized constant item!");
				}
				else if ( dp->attr->dimVect )	// array defined
				{
					int *v = dp->attr->dimVect;
					for (int i = 0; i < v[0]; i++) {
						if ( v[i+1] <= 0 ) {
							errPrint("unknown array dimension!");
							break;
						}
					}
				}
			}
			break;

		case FUNC_DECL:
			np = op->op[0];	// get function head
			fname = np->opr.op[0]->id.name;

			if ( curDlink->search(fname, LOCAL_SEARCH) )
				errPrint("name redefined!", fname);		// name used as data!
			else
			{
				bool func_def = (op->op[1] != NULL);	// func body exitst?
				bool err = false;

				Fnode *fp = funcLink->search(fname);	// function existed already?
				if ( fp == NULL )	// No, it's first time ...
				{
					fp = new Fnode(fname, 0);			// create Fnode
					fp->attr = cloneAttr(ap);			// copy it's (return) type.

					funcLink->add(fp);					// add Fnode to Flink
					curDlink->addChild(fp->dlink);		// add par dlink to parent

					if ( np->opr.nops > 1 ) { 			// with parameter?
						node *pp = np->opr.op[1]; 		// parameter list
						fp->elipsis = pp->list.elipsis;
						for (int i = 0; i < pp->list.nops; i++)
						{
							node *parp = pp->list.ptr[i];

							if ( func_def && fp->dlink->search(parp->id.name, LOCAL_SEARCH) )
								errPrint("parameter name duplicatated!", NULL), err = true;

							Dnode *dp = fp->dlink->add(parp, parp->id.attr, 0);
							dp->func = fp->name;
						}
					}
				}
				else if ( fp->endLbl > 0 && func_def )
				{
					errPrint("function redefined!", fname), err = true;
				}
				else	// function has been declaired/defined - need confirming
				{
					if ( (np->opr.nops <= 1 && fp->dlink->dataCount() != 0) ||
						 (np->opr.nops >  1 && fp->dlink->dataCount() == 0) ||
						 (np->opr.nops >  1 && np->opr.op[1]->list.nops != fp->dlink->dataCount()) )
					{
						 errPrint("function parameter count conflict!", fname), err = true;
					}
					else if ( np->opr.nops > 1 )
					{
						np = np->opr.op[1];	// get parameter list
						for (int i = 0; !err && i < np->list.nops; i++)
						{
							Dnode *dp = fp->dlink->get(i);
							node  *pp = np->list.ptr[i];

							if ( cmpAttr(dp->attr, pp->id.attr) )
								errPrint("parameter type confilict!", fname), err = true;
							else if ( dp->fptrCheck(pp) != true )
								errPrint("parameter type confilict!", fname), err = true;
							else if ( func_def )
							{
								if ( !fp->dlink->nameCheck(pp->id.name, i) )
									errPrint("par name duplicated!", fname), err = true;
								else
									dp->nameUpdate(pp->id.name);
							}
						}
					}
				}

				if ( func_def && !err )
				{
					curDlink = fp->dlink;		// update current Dlink
					curFnode = fp;
					addPnode(&mainPcode, new Pnode(P_FUNC_BEG, ptrItem(fp)));
					fp->endLbl = getLbl();		// set end label for function
					run (op->op[1]);			// scan function body

					addPnode(&mainPcode, new Pnode(LABEL, lblItem(fp->endLbl)));
					addPnode(&mainPcode, new Pnode(P_FUNC_END, ptrItem(fp)));
					curFnode = NULL;
					curDlink = fp->dlink->parent;// recover current Dlink
				}
			}
			break;


		default:
			run2(op);
			break;
	}
}

